/* vim: set expandtab shiftwidth=4: -*- mode: c++; c-basic-offset: 4; tab-width: 8; indent-tabs-mode: nil -*-
Compositor
An easy-to-use image compositing program.
Copyright (C) 2006 Wesley Crossman
Email: wesley@crossmans.net

You can redistribute and/or modify this software under the terms of the GNU
General Public License as published by the Free Software Foundation;
either version 2 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this
program; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
Suite 330, Boston, MA 02111-1307 USA */

#ifndef TRANSFORMATION_H
#define TRANSFORMATION_H

#include "imagerotate.h"
#include <QApplication>
#include <QList>
#include <QPainter>
#include <QImage>
#include <QtXml>
#include <iostream>
#include <cassert>

class Transformation {
public:
    virtual QImage transform(const QImage &img) = 0;
    virtual ~Transformation() {};
};

#define TRANSFORM_ASSERT(v, m) if (!(v)) {qCritical() << QString("Line %1 -- <%2> -- %3").arg(element.lineNumber()).arg(element.nodeName()).arg(m); return NULL;}
#define TRANSFORM_WARN_ASSERT(v, m) if (!(v)) {qCritical() << QString("Line %1 -- <%2> -- %3").arg(element.lineNumber()).arg(element.nodeName()).arg(m);}

class Rotation : public Transformation {
private:
    QDomElement element;
    QString operation;
    Rotation() {}
public:
    static QRegExp regexp;
    static Rotation *generate(const QDomElement &element) {
        Rotation *r = new Rotation();
        r->element = element;
        TRANSFORM_ASSERT(element.hasAttribute("value"),
                "Element should have a \"value\" attribute.");
        TRANSFORM_ASSERT(element.attributes().size() == 1,
                "Element should only have one attribute, \"value\".");
        TRANSFORM_ASSERT(regexp.exactMatch(element.attribute("value")),
                "\"value\" needs a value of \"left\", \"right\", \"l\", \"r\""
                ", or a rotation value in degrees.");
        r->operation = element.attribute("value");
        return r;
    }
    virtual QImage transform(const QImage &img) {
        int opInt = operation.toInt() % 360;
        if (operation.startsWith("r") || opInt==90) return rotate90(img);
        else if (operation.startsWith("l") || opInt==270) return rotate270(img);
        else {
            QMatrix matrix;
            matrix.rotate(opInt);
            return img.transformed(matrix, Qt::SmoothTransformation);
        }
    }
};
QRegExp Rotation::regexp("r(ight)?|l(eft)?|-?[0-9]+");

class Flip : public Transformation {
private:
    QDomElement element;
    QString operation;
    Flip() {}
public:
    static Flip *generate(const QDomElement &element) {
        Flip *f = new Flip();
        f->element = element;
        TRANSFORM_ASSERT(element.hasAttribute("value"),
                "Element should have a \"value\" attribute.");
        TRANSFORM_ASSERT(element.attributes().size() == 1,
                "Element should only have one attribute, \"value\".");
        TRANSFORM_ASSERT(QRegExp("h(orizontal)?|v(ertical)?|b(oth)?")
                .exactMatch(element.attribute("value")),
                "\"value\" needs a value of \"h\", \"v\", \"b\","
                "\"horizontal\", \"vertical\", or \"both\".");
        f->operation = element.attribute("value");
        return f;
    }
    virtual QImage transform(const QImage &img) {
        if (operation.startsWith("v")) return img.mirrored(false, true);
        else if (operation.startsWith("h")) return img.mirrored(true, false);
        else return img.mirrored(true, true);
    }
};

class Crop : public Transformation {
private:
    QDomElement element;
    QString operation;
    Crop() {}
public:
    static QRegExp regexp;
    static Crop *generate(const QDomElement &element) {
        Crop *c = new Crop();
        c->element = element;
        TRANSFORM_ASSERT(element.hasAttribute("value"),
                "Element should have a \"value\" attribute.");
        TRANSFORM_ASSERT(element.attributes().size() == 1,
                "Element should only have one attribute, \"value\".");
        TRANSFORM_ASSERT(
                regexp.exactMatch(element.attribute("value")),
                "\"value\" should be in the form of \"50, 50, 100, 100\".");
        c->operation = element.attribute("value");
        return c;
    }
    virtual QImage transform(const QImage &img) {
        regexp.exactMatch(operation);
        int x = regexp.cap(1).toInt(), y = regexp.cap(2).toInt();
        int w = regexp.cap(3).toInt(), h = regexp.cap(4).toInt();
        TRANSFORM_WARN_ASSERT(x < img.width(), "x is larger then the image width.");
        TRANSFORM_WARN_ASSERT(y < img.height(), "y is larger then the image height.");
        TRANSFORM_WARN_ASSERT(x+w <= img.width(), "x+width is larger then the image width.");
        TRANSFORM_WARN_ASSERT(y+h <= img.height(), "y+height is larger then the image height.");
        return img.copy(x, y,
                (w == -1) ? (img.width() - x) : w,
                (h == -1) ? (img.height() - y) : h);
    }
};
QRegExp Crop::regexp("([0-9]+) *, *([0-9]+) *, *(-?[0-9]+) *, *(-?[0-9]+)");

class Resize : public Transformation {
private:
    QDomElement element;
    QString operation;
    int reTrue;
    Resize() {reTrue = -1;}
public:
    static QRegExp regexp[4];
    static Resize *generate(const QDomElement &element) {
        Resize *c = new Resize();
        c->element = element;
        TRANSFORM_ASSERT(element.hasAttribute("value"),
                "Element should have a \"value\" attribute.");
        TRANSFORM_ASSERT(element.attributes().size() == 1,
                "Element should only have one attribute, \"value\".");
        for (int a=0;a<4;++a) {
            if (regexp[a].exactMatch(element.attribute("value"))) c->reTrue = a;
        }
        TRANSFORM_ASSERT(
                c->reTrue > -1,
                "\"value\" can be in the form of \"50%\", \"10, 10\", \"50%,40%\", etc.."
                "See documentation for more information.");
        c->operation = element.attribute("value");
        return c;
    }
    virtual QImage transform(const QImage &img) {
        if (reTrue == 0) {
            regexp[0].exactMatch(operation);
            int w = regexp[0].cap(1).toInt(), h = regexp[0].cap(2).toInt();
            return img.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
        } else if (reTrue == 1) {
            regexp[1].exactMatch(operation);
            double percent = regexp[1].cap(1).toDouble()/100.0;
            return img.scaled(img.size() * percent,
                    Qt::KeepAspectRatio, Qt::SmoothTransformation);
        } else if (reTrue == 2) {
            regexp[2].exactMatch(operation);
            double percentW = regexp[2].cap(1).toDouble()/100.0;
            double percentH = regexp[2].cap(2).toDouble()/100.0;
            return img.scaled(img.width() * percentW, img.height() * percentH,
                    Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
        } else {
            int w = 0, h = 0;
            int value = regexp[3].cap(1).toInt();
            double aspect = double(img.width()) / double(img.height());
            QString dimension = regexp[3].cap(2);
            if (img.width() == img.height()) {
                w = value;
                h = value;
            } else if (dimension == "w" || (dimension == "min" && aspect < 1.0)
                        || (dimension == "max" && aspect >= 1.0) ) {
                w = value;
                h = int(w / aspect);
            } else if (dimension == "h" || (dimension == "min" && aspect > 1.0)
                        || (dimension == "max" && aspect <= 1.0) ) {
                h = value;
                w = int(h * aspect);
            } else {
                assert(0);
            }
            if (w < 0) w = 1;
            if (h < 0) h = 1;
            return img.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
        }
    }
};
QRegExp Resize::regexp[4] = {QRegExp("([0-9]+) *[,w] *([0-9]+) *h?"), QRegExp("([0-9]+) *%"), QRegExp("([0-9]+) *%[,w] *([0-9]+) *%h?"), QRegExp("([0-9]+) *(w|h|min|max)")};


Transformation *getTransformation(QDomElement);

Transformation *getTransformation(QDomElement element) {
    if (element.tagName() == "rotate") {
        return Rotation::generate(element);
    } else if (element.tagName() == "flip") {
        return Flip::generate(element);
    } else if (element.tagName() == "crop") {
        return Crop::generate(element);
    } else if (element.tagName() == "resize") {
        return Resize::generate(element);
    } else {
        qCritical() << "Element \"" << element.tagName()
                << "\" (line " << element.lineNumber() << ") is unrecognized.";
        return NULL;
    }
}

#endif

